package com.lagou.edu.factory;

import com.lagou.edu.annotation.*;
import com.lagou.edu.utils.ReflectionUtils;

import javax.annotation.Resource;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;

public class AnnotationBeanFactory {
    /**
     * 任务一：扫描注解类，通过反射技术实例化对象并且存储待用（map集合）
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     */

    private static Map<String, Object> map = new HashMap<>();     // 存储对象

    private static Map<String, String> aliasMap = new HashMap<>(); // 别名映射<别名, 全限定名>

    // 保存用户指定扫描包路径下的所有类
    private static List<String> classNames = new ArrayList<>();
    // 标记有@Transactional的类
    private static List<String> aopClassNames = new ArrayList<>();
    private static String packageName = "com.lagou.edu";

    static {
        /**
         * 1. 注意此处省略了@Configuration，@EnableAspectJAutoProxy，@EnableTransactionManagement，@ComponentScan等
         * 默认从 com.lagou.edu 开始扫描
         */


        // 扫描包
        doScanner(packageName);

        // 通过反射实例化,放到map
        doInstance();

        // 设置依赖
        doReference();

        // 声明式事务AOP处理 (有@Transactional注解的对象生成proxy，并替换map中的实例)
        doTransactionAop();

    }

    static void doTransactionAop() {
        if (!aopClassNames.isEmpty()) {
            aopClassNames.forEach(className -> {
                Object instance = map.get(className);
                // TODO 判断实例是否实现了接口, 有则使用jdk动态代理，没有则使用cglib动态代理；目前没有实现 @EnableAspectJAutoProxy
                // TODO 此处简单的认为有接口则默认使用jdk动态代理（此处不严谨,可能会实现多个接口,需要验证）
                boolean hasInterface = instance.getClass().getInterfaces().length > 0 ? true : false;
                ProxyFactory proxyFactory = getBean(ProxyFactory.class);

                Object proxy;
                if (hasInterface) {
                    proxy = proxyFactory.getJdkProxy(instance);
                } else {
                    proxy = proxyFactory.getCglibProxy(instance);
                }

                map.put(className, proxy);
            });
        }
    }

    static void doScanner(String packageName) {
        //把所有的.替换成/
        URL url = Thread.currentThread().getContextClassLoader().getResource(packageName.replaceAll("\\.", "/"));
        File dir = new File(url.getFile());
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                //递归读取包
                doScanner(packageName + "." + file.getName());
            } else {
                String className = packageName + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }

    private static String getImplementClass(String clazz) {
        Iterator<Map.Entry<String, Object>> entry = map.entrySet().iterator();
        List<String> impls = new ArrayList<>();
        while (entry.hasNext()) {
            Map.Entry<String, Object> next = entry.next();
            Class<?>[] interfaces = next.getValue().getClass().getInterfaces();
            if (Arrays.asList(interfaces).stream().filter(i -> i.getCanonicalName().equals(clazz)).findAny().isPresent()) {
                impls.add(next.getKey());
            }
        }

        if (impls.isEmpty())
            throw new RuntimeException("没有找到接口的子类实现");
        else if (impls.size() > 1) {
            // 查找别名
            Iterator<Map.Entry<String, String>> entry1 = aliasMap.entrySet().iterator();
            while (entry1.hasNext()){
                Map.Entry<String, String> next = entry1.next();
                if (next.getValue().equals(clazz)){
                    return next.getKey();
                }
            }
            throw new RuntimeException("查询到多个子类实现，请指定别名");
        } else return impls.get(0);
    }

    static void doReference() {
        if (map.isEmpty()) return;
        Iterator<Map.Entry<String, Object>> entry = map.entrySet().iterator();
        while (entry.hasNext()) {
            try {
                Map.Entry<String, Object> next = entry.next();
                Object obj = next.getValue();
                Field[] fields = obj.getClass().getDeclaredFields();
                if (fields != null && fields.length > 0) {
                    Arrays.stream(fields).forEach(field -> {
                        // TODO 目前不校验static, 以及 @Autowired 里的属性, 也不做set注入和构造注入, 默认只实现属性注入
                        if (field.isAnnotationPresent(Autowired.class) || field.isAnnotationPresent(Resource.class)) {
                            ReflectionUtils.makeAccessible(field);
                            try {
                                //判断是接口类型
                                String key;
                                if (field.getType().isInterface()) {
                                    key = getImplementClass(field.getType().getCanonicalName());
                                } else {
                                    key = field.getType().getCanonicalName();
                                }
                                field.set(obj, map.get(key));
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
    }

    static void doInstance() {
        if (classNames.isEmpty()) return;
        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);
                // TODO 不处理自身扫描问题(不要再注解上面加@Component即可)
                if (clazz.isAnnotationPresent(Component.class) || clazz.isAnnotationPresent(Service.class) || clazz.isAnnotationPresent(Repository.class)) {
                    //map.put(toLowerFirstWord(clazz.getSimpleName()), clazz.newInstance());

                    // 1. 实例化保存到map
                    map.put(clazz.getCanonicalName(), clazz.newInstance());

                    // 2. 标识需要AOP增强处理的实例
                    if (clazz.isAnnotationPresent(Transactional.class)) {
                        aopClassNames.add(className);
                    }

                    // 3. 处理别名 (暂时不处理@Autowired等别名)
                    String alias = null;
                    if (clazz.getAnnotation(Component.class) == null) {
                        if (clazz.getAnnotation(Service.class) == null) {
                            if (clazz.getAnnotation(Repository.class) != null) {
                                alias = clazz.getAnnotation(Repository.class).value();
                            }
                        } else {
                            alias = clazz.getAnnotation(Service.class).value();
                        }
                    } else {
                        alias = clazz.getAnnotation(Component.class).value();
                    }
                    if (null != alias && !alias.isEmpty())
                        aliasMap.put(alias, clazz.getCanonicalName());
                } else {
                    continue;
                }

            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
    }

    /**
     * 此时若有多个实现，则会报错
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<?> clazz) {
        if (clazz.isInterface())
            return (T) map.get(getImplementClass(clazz.getCanonicalName()));
        return (T) map.get(clazz.getCanonicalName());
    }


    /**
     * 支持别名 或 全限定类名
     *
     * @param name
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name) {
        Object instance = map.get(name);
        if (null == instance)
            instance = map.get(aliasMap.get(name));

        return (T) instance;
    }
}
